﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Abp.Authorization.Users;
using Abp.BackgroundJobs;
using Abp.Dependency;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.Extensions;
using Abp.IdentityFramework;
using Abp.Localization;
using Abp.Notifications;
using Abp.ObjectMapping;
using Abp.Organizations;
using Abp.Threading;
using Abp.UI;
using Microsoft.AspNetCore.Identity;
using pandx.Mulan.Authorization.Roles;
using pandx.Mulan.Authorization.Users.Importing.Dto;
using pandx.Mulan.Notifications;
using pandx.Mulan.Storage;

namespace pandx.Mulan.Authorization.Users.Importing
{
    public class ImportUsersFromExcelJob:BackgroundJob<ImportUsersFromExcelJobArgs>,ITransientDependency
    {
        private readonly IUserListExcelDataReader _userListExcelDataReader;
        private readonly IInvalidUserExporter _invalidUserExporter;
        private readonly IEnumerable<IPasswordValidator<User>> _passwordValidators;
        private readonly IPasswordHasher<User> _passwordHasher;
        private readonly IAppNotifier _appNotifier;
        private readonly IBinaryObjectManager _binaryObjectManager;
        private readonly IObjectMapper _objectMapper;
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly IFileManager _fileManager;
        private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;
        public UserManager UserManager { get; set; }
        public RoleManager RoleManager { get; set; }
        

        public ImportUsersFromExcelJob(
            IRepository<OrganizationUnit,long> organizationUnitRepository,
            IUserListExcelDataReader userListExcelDataReader,
            IInvalidUserExporter invalidUserExporter,
            IEnumerable<IPasswordValidator<User>> passwordValidators,
            IPasswordHasher<User> passwordHasher,
            IAppNotifier appNotifier,
            IBinaryObjectManager binaryObjectManager,
            IObjectMapper objectMapper,
            IUnitOfWorkManager unitOfWorkManager,
            IFileManager fileManager
        )
        {
            _organizationUnitRepository = organizationUnitRepository;
            _userListExcelDataReader = userListExcelDataReader;
            _invalidUserExporter = invalidUserExporter;
            _passwordValidators = passwordValidators;
            _passwordHasher = passwordHasher;
            _appNotifier = appNotifier;
            _binaryObjectManager = binaryObjectManager;
            _objectMapper = objectMapper;
            _unitOfWorkManager = unitOfWorkManager;
            _fileManager = fileManager;
        }

        public override void Execute(ImportUsersFromExcelJobArgs args)
        {
            var users = GetUserListFromExcelOrNull(args);
            if (users == null || !users.Any())
            {
                SendInvalidExcelNotification(args);
                return;
            }

            CreateUsers(args, users);
        }

        private List<ImportUserDto> GetUserListFromExcelOrNull(ImportUsersFromExcelJobArgs args)
        {
            using var uow = _unitOfWorkManager.Begin();
            using (CurrentUnitOfWork.SetTenantId(args.TenantId))
            {
                try
                {
                    var file = AsyncHelper.RunSync(() => _binaryObjectManager.GetOrNullAsync(args.BinaryObjectId));
                    return _userListExcelDataReader.GetUsersFromExcel(file.Bytes);
                }
                catch (Exception)
                {
                    return null;
                }
                finally
                {
                    uow.Complete();
                }
            }
        }

        private void CreateUsers(ImportUsersFromExcelJobArgs args, List<ImportUserDto> users)
        {
            var invalidUsers = new List<ImportUserDto>();
            foreach (var user in users)
            {
                using var uow1 = _unitOfWorkManager.Begin();
                using (CurrentUnitOfWork.SetTenantId(args.TenantId))
                {
                    if (user.CanBeImported())
                    {
                        try
                        {
                            AsyncHelper.RunSync(() => CreateUserAsync(user));
                        }
                        catch (UserFriendlyException exception)
                        {
                            user.Exception = exception.Message;
                            invalidUsers.Add(user);
                        }
                        catch (Exception exception)
                        {
                            user.Exception = exception.Message;
                            invalidUsers.Add(user);
                        }
                    }
                    else
                    {
                        invalidUsers.Add(user);
                    }
                }
                uow1.Complete();
            }
            using var uow2 = _unitOfWorkManager.Begin();
            using (CurrentUnitOfWork.SetTenantId(args.TenantId))
            {
                AsyncHelper.RunSync(() => ProcessImportUsersResultAsync(args, invalidUsers));
            }
            uow2.Complete();
        }

        private async Task CreateUserAsync(ImportUserDto input)
        {
            var tenantId = CurrentUnitOfWork.GetTenantId();
            if (tenantId.HasValue)
            {
            }

            var user = _objectMapper.Map<User>(input);
            user.Password = input.Password;
            user.TenantId = tenantId;
            user.Surname = input.Name;
            user.Gender = Gender.Male;

            if (!input.GenderDisplayName.IsNullOrEmpty())
            {
                user.Gender = input.GenderDisplayName == "女" ? Gender.Female : Gender.Male;
            }

            if (!input.Password.IsNullOrEmpty())
            {
                await UserManager.InitializeOptionsAsync(tenantId);
                foreach (var validator in _passwordValidators)
                {
                    (await validator.ValidateAsync(UserManager,user,input.Password)).CheckErrors();
                }

                user.Password = _passwordHasher.HashPassword(user, input.Password);
            }

            user.OrganizationUnits = new List<UserOrganizationUnit>();
            foreach (var code in input.AssignedOrganizationUnits)
            {
                var organizationUnit = await _organizationUnitRepository.FirstOrDefaultAsync(ou => ou.Code == code);
                if (organizationUnit != null)
                {
                    user.OrganizationUnits.Add(new UserOrganizationUnit(tenantId, user.Id, organizationUnit.Id));
                }
            }

            user.Roles = new List<UserRole>();
            var roleList = RoleManager.Roles.ToList();

            foreach (var roleName in input.AssignedRoles)
            {
                var correspondingRoleName = GetRoleNameForDisplayName(roleName, roleList);
                var role = await RoleManager.GetRoleByNameAsync(correspondingRoleName);
                user.Roles.Add(new UserRole(tenantId,user.Id,role.Id));
            }

            (await UserManager.CreateAsync(user)).CheckErrors();
        }

        private async Task ProcessImportUsersResultAsync(ImportUsersFromExcelJobArgs args,List<ImportUserDto> invalidUsers)
        {
            if (invalidUsers.Any())
            {
                var file = _invalidUserExporter.ExportToExcel(invalidUsers);

                AsyncHelper.RunSync(()=>_fileManager.SaveFile(file.FileToken, file.FileName));
                await _appNotifier.SomeUsersCannotBeImported(args.User, file.FileToken, file.FileType, file.FileName);
                
            }
            else
            {
                await _appNotifier.SendCommonMessageAsync(args.User, "全部用户导入成功","全部用户已经导入成功，请在用户模块中查看。",
                    NotificationSeverity.Success);
            }
        }

        private void SendInvalidExcelNotification(ImportUsersFromExcelJobArgs args)
        {
            using var uow = _unitOfWorkManager.Begin();
            using (CurrentUnitOfWork.SetTenantId(args.TenantId))
            {
                AsyncHelper.RunSync(() => _appNotifier.SendCommonMessageAsync(args.User,"选择的文件不能被识别为Excel文件","用户导入仅支持Excel文件，您选择的文件不能被识别，请检查。",NotificationSeverity.Warn));
            }
            uow.Complete();
        }

        private string GetRoleNameForDisplayName(string displayName, List<Role> roleList)
        {
            return roleList.FirstOrDefault(r => r.DisplayName?.ToLowerInvariant() == displayName?.ToLowerInvariant())
                ?.Name;
        }
    }
}
